﻿using System;
using System.Linq;
using System.Reflection;
using Castle.Core;
using Castle.MicroKernel;
using Castle.MicroKernel.ModelBuilder;
using Castle.MicroKernel.ModelBuilder.Inspectors;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Conversion;
using Castle.MicroKernel.Util;
using Castle.Windsor;
using NUnit.Framework;

namespace WindsorInitConfig {
    [TestFixture]
    public class AttributeServiceOverride {
        [Test]
        public void tt() {
            var container = new WindsorContainer();
            var modelBuilder = container.Kernel.ComponentModelBuilder;
            var oldContributor = modelBuilder.Contributors.OfType<ConstructorDependenciesModelInspector>().Single();
            modelBuilder.RemoveContributor(oldContributor);
            modelBuilder.AddContributor(new CustomConstructorComponentModelContributor());
            container.Register(Component.For<IIntf>().ImplementedBy<Dep1>().Named("d1"));
            container.Register(Component.For<IIntf>().ImplementedBy<Dep2>().Named("d2"));
            container.Register(Component.For<MyService>());
            var s = container.Resolve<MyService>();
            Assert.IsInstanceOfType(typeof (Dep2), s.F);
        }

        public class MyService {
            private readonly IIntf f;

            public MyService([Selector("d2")] IIntf f) {
                this.f = f;
            }

            public IIntf F {
                get { return f; }
            }
        }

        /// <summary>
        /// Mostly copied from Windsor's original ConstructorDependenciesModelInspector
        /// </summary>
        /// <remarks>
        /// Copyright 2004-2009 Castle Project - http://www.castleproject.org/
        /// 
        /// Licensed under the Apache License, Version 2.0 (the "License");
        /// you may not use this file except in compliance with the License.
        /// You may obtain a copy of the License at
        /// 
        ///     http://www.apache.org/licenses/LICENSE-2.0
        /// 
        /// Unless required by applicable law or agreed to in writing, software
        /// distributed under the License is distributed on an "AS IS" BASIS,
        /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        /// See the License for the specific language governing permissions and
        /// limitations under the License.
        /// </remarks>
        public class CustomConstructorComponentModelContributor : IContributeComponentModelConstruction {
            private IConversionManager converter;

            public virtual void ProcessModel(IKernel kernel, ComponentModel model) {
                if (converter == null) {
                    converter = (IConversionManager) kernel.GetSubSystem(SubSystemConstants.ConversionManagerKey);
                }

                var targetType = model.Implementation;

                var constructors = targetType.GetConstructors(BindingFlags.Public | BindingFlags.Instance);

                foreach (var constructor in constructors) {
                    // We register each public constructor
                    // and let the ComponentFactory select an 
                    // eligible amongst the candidates later
                    model.Constructors.Add(CreateConstructorCandidate(model, constructor));
                }
            }

            protected virtual ConstructorCandidate CreateConstructorCandidate(ComponentModel model, ConstructorInfo constructor) {
                var parameters = constructor.GetParameters();

                var dependencies = new DependencyModel[parameters.Length];

                for (var i = 0; i < parameters.Length; i++) {
                    var parameter = parameters[i];

                    var paramType = parameter.ParameterType;

                    var attr = parameter.GetCustomAttributes(typeof (SelectorAttribute), true).Cast<SelectorAttribute>().SingleOrDefault();

                    if (attr != null) {
                        dependencies[i] = new DependencyModel(DependencyType.ServiceOverride, attr.Id, paramType, false);
                    } else {
                        // This approach is somewhat problematic. We should use
                        // another strategy to differentiate types and classify dependencies
                        if (converter.IsSupportedAndPrimitiveType(paramType)) {
                            dependencies[i] = new DependencyModel(DependencyType.Parameter, parameter.Name, paramType, false);
                        } else if (String.IsNullOrEmpty(parameter.Name) == false) {
                            var modelParameter = model.Parameters[parameter.Name];

                            if (modelParameter != null && ReferenceExpressionUtil.IsReference(modelParameter.Value)) {
                                var key = ReferenceExpressionUtil.ExtractComponentKey(modelParameter.Value);

                                dependencies[i] = new DependencyModel(DependencyType.ServiceOverride, key, paramType, false);
                            } else {
                                dependencies[i] = new DependencyModel(DependencyType.Service, parameter.Name, paramType, false);
                            }
                        } else {
                            dependencies[i] = new DependencyModel(DependencyType.Service, null, paramType, false);
                        }
                    }
                }

                return new ConstructorCandidate(constructor, dependencies);
            }
        }

        public class SelectorAttribute : Attribute {
            private readonly string id;

            public SelectorAttribute(string id) {
                this.id = id;
            }

            public string Id {
                get { return id; }
            }
        }

        public interface IIntf {}

        public class Dep1 : IIntf {}

        public class Dep2 : IIntf {}
    }
}